package com.quan.commons.utils;

import lombok.extern.slf4j.Slf4j;

import java.text.NumberFormat;
import java.util.Arrays;

/**
 * 秒表工具
 * 支持多级方法嵌套
 * 1万次空跑总耗时41毫秒
 * eg1:
 * StopWatch w1 = new StopWatch("例子1")
 * final String taskName = "task1"
 * w1.start(taskName)
 * w1.stop(taskName)
 * log.info("任务结束: {}", w1.prettyPrint())
 * <p>
 * eg2:
 * StopWatch w1 = new StopWatch("例子1")
 * final String taskName = "task1"
 * try {
 * w1.start(taskName)
 * methodMayThrowException()
 * } finally {
 * w1.stop(taskName)
 * }
 * log.info("任务结束: {}", w1.prettyPrint())
 *
 * @author hp
 * @since 2020-12-08
 **/
@Slf4j
public class StopWatch {

    private String stopWatchName;

    /**
     * 所有任务构成的链表
     */
    private TaskInfo[] tasks;

    /**
     * 当前任务
     */
    private TaskInfo currentTask = null;

    /**
     * 任务个数
     */
    private int count = 0;

    /**
     * 数组大小
     */
    private int arraySize;

    /**
     * 当前所处的层级
     */
    private int currentLevel;

    /**
     * 当前任务的开始时间
     */
    private long startTimeMillis;

    /**
     * 一级任务总耗时
     */
    private long l1TotalTimeMillis;

    /**
     * 初始化，开始全局计时
     */
    public StopWatch() {
        this.currentLevel = 1;
        this.startTimeMillis = System.currentTimeMillis();
        this.arraySize = 4;
        this.tasks = new TaskInfo[this.arraySize];
    }

    /**
     * 初始化，开始全局计时
     *
     * @param stopWatchName 秒表名称
     */
    public StopWatch(String stopWatchName) {
        this();
        this.stopWatchName = stopWatchName;
    }

    /**
     * 启动一个任务
     *
     * @param taskName 任务名称
     */
    public void start(String taskName) {
        // 当前有正在计时的任务，层级+1
        if (null != this.currentTask && !this.currentTask.isStop()) {
            this.currentLevel++;
        }
        this.currentTask = new TaskInfo(taskName, System.currentTimeMillis(), this.currentLevel);
        count++;
        ensureCapacityInternal(count);
        tasks[count - 1] = this.currentTask;
    }

    /**
     * task数组扩容实现
     *
     * @param minCapacity 最小容量需求
     */
    private void ensureCapacityInternal(int minCapacity) {
        if (minCapacity > arraySize) {
            // 定义原容量长度
            int oldCapacity = arraySize;
            // 新容量扩大到原容量的1.5倍，右移一位相关于原数值除以2，加上原容量长度
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            tasks = Arrays.copyOf(tasks, newCapacity);
            this.arraySize = newCapacity;
        }
    }

    /**
     * 停止当前任务
     *
     * @param taskName 任务名称
     */
    public void stop(String taskName) {
        if (null == this.currentTask) {
            log.warn("当前没有正在计时的任务，秒表暂停失败，请确认stop方法的调用次数是否与start方法调用次数匹配");
            return;
        }

        // 对应因异常导致部分task未关闭的情况
        if (!taskName.equals(this.currentTask.getTaskName())) {
            this.currentTask.interrupt();
            // 正序遍历，从第一个名称一致的task开始，之后的task全部意外终止
            boolean begin2Down = false;
            for (int i = 0; i <= count - 1; i++) {
                TaskInfo next = tasks[i];
                // 找到匹配的task之后:
                // 1、该task正常stop
                // 2、往前找最近一个未关闭的task作为current -> 找到则退出子循环
                // 3、往后找所有未关闭的task并关闭 -> 完整遍历
                if (taskName.equals(next.getTaskName()) && !next.isStop()) {
                    long keepTimeMillis = next.stop();
                    this.currentTask = null;
                    l1TotalTimeMillis += keepTimeMillis;
                    if (i > 1) {
                        for (int j = i - 1; j >= 0; j--) {
                            TaskInfo prev = tasks[j];
                            if (!prev.isStop()) {
                                this.currentTask = prev;
                                break;
                            }
                        }
                    }
                    begin2Down = true;
                    continue;
                }
                if (begin2Down && !tasks[i].isStop()) {
                    tasks[i].interrupt();
                }
            }
            if (null != this.currentTask) {
                this.currentLevel = this.currentTask.getLevel();
            } else {
                this.currentLevel = 1;
            }
            return;
        }

        long keepTimeMillis = this.currentTask.stop();

        // 如果当前任务是一级任务，说明所有任务都停止了
        if (1 == this.currentTask.getLevel()) {
            l1TotalTimeMillis += keepTimeMillis;
            this.currentTask = null;
            this.currentLevel = 1;
            return;
        }

        // 往前遍历寻找最近一个未关闭task
        for (int i = count - 1; i >= 0; i--) {
            TaskInfo prev = tasks[i];
            if (prev.isStop()) {
                // 往前遍历追溯到一个已经完结的一级任务，说明所有任务都停止了
                if (1 == prev.getLevel()) {
                    this.currentTask = null;
                    this.currentLevel = 1;
                    return;
                }
                continue;
            }
            this.currentTask = prev;
            this.currentLevel = this.currentTask.getLevel();
            return;
        }
    }

    public void setStopWatchName(String stopWatchName) {
        this.stopWatchName = stopWatchName;
    }

    /**
     * 返回一个简短的描述，并截止计算整个StopWatch的耗时
     *
     * @return java.lang.String
     */
    public String stopAndShortSummary() {
        long totalTimeMillis = System.currentTimeMillis() - startTimeMillis;
        return "[" + this.stopWatchName + "] 总耗时: " + totalTimeMillis + "ms, 执行任务: " + count + "个, 耗时: " + l1TotalTimeMillis + "ms, 未统计逻辑耗时: " + (totalTimeMillis - l1TotalTimeMillis) + "ms";
    }

    /**
     * 打印任务耗时
     *
     * @return java.lang.String
     */
    public String prettyPrint() {
        if (count > 0 && !tasks[count - 1].isStop()) {
            for (int i = 0; i < count; i++) {
                if (!tasks[i].isStop()) {
                    long keepTimeMillis = tasks[i].interrupt();
                    if (1 == tasks[i].getLevel()) {
                        l1TotalTimeMillis += keepTimeMillis;
                    }
                }
            }
        }
        StringBuilder sb = new StringBuilder(stopAndShortSummary());
        if (count <= 0) {
            return sb.toString();
        }
        sb.append('\n');
        sb.append("-----------------------------------------\n");
        sb.append("ms     %     result    Task name\n");
        sb.append("-----------------------------------------\n");
        NumberFormat nf = NumberFormat.getNumberInstance();
        nf.setMinimumIntegerDigits(5);
        nf.setGroupingUsed(false);
        NumberFormat pf = NumberFormat.getPercentInstance();
        pf.setMinimumIntegerDigits(3);
        pf.setGroupingUsed(false);
        for (int i = 0; i < count; i++) {
            sb.append(nf.format(tasks[i].getKeepTimeMillis())).append("  ");
            if (0L == l1TotalTimeMillis) {
                sb.append("100%").append(" ");
            } else {
                sb.append(pf.format(tasks[i].getKeepTimeMillis() * 1.00 / l1TotalTimeMillis)).append("  ");
            }

            if (tasks[i].isFinish()) {
                sb.append("finish").append("    ");
            } else {
                sb.append("interrupt").append(" ");
            }
            int taskLevel = tasks[i].getLevel();
            if (taskLevel > 1) {
                for (int j = taskLevel - 1; j > 0; j--) {
                    sb.append("> ");
                }
            }
            sb.append(tasks[i].getTaskName()).append("\n");
        }
        return sb.toString();
    }


    /**
     * 任务信息类
     *
     * @author hp
     * @since 2020-12-23
     */
    public static final class TaskInfo {

        /**
         * 任务名称
         */
        private final String taskName;

        /**
         * 任务开始时间
         */
        private final long beginTimeMillis;

        /**
         * 任务持续时间
         */
        private long keepTimeMillis;

        /**
         * 任务层级
         */
        private int level;

        /**
         * 任务是否已结束
         */
        private boolean stop = false;

        /**
         * 任务是否手动结束
         */
        private boolean finish = false;

        TaskInfo(String taskName, long beginTimeMillis, int level) {
            this.taskName = taskName;
            this.beginTimeMillis = beginTimeMillis;
            this.level = level;
        }

        /**
         * 返回任务名称
         *
         * @return java.lang.String
         */
        public String getTaskName() {
            return this.taskName;
        }

        /**
         * 返回毫秒级耗时
         *
         * @return long
         */
        public long getKeepTimeMillis() {
            return this.keepTimeMillis;
        }

        /**
         * 返回执行结果
         *
         * @return boolean
         */
        public boolean isFinish() {
            return this.finish;
        }

        /**
         * 返回是否已结束
         *
         * @return boolean
         */
        public boolean isStop() {
            return this.stop;
        }

        /**
         * 返回当前任务层级
         *
         * @return int
         */
        public int getLevel() {
            return this.level;
        }

        /**
         * 结束当前任务
         *
         * @return long
         */
        public long stop() {
            this.keepTimeMillis = System.currentTimeMillis() - this.beginTimeMillis;
            this.stop = true;
            this.finish = true;
            return keepTimeMillis;
        }

        /**
         * 意外终止当前任务(例如抛出了异常导致未执行stop)
         *
         * @return long
         */
        public long interrupt() {
            this.keepTimeMillis = System.currentTimeMillis() - this.beginTimeMillis;
            this.stop = true;
            this.finish = false;
            return keepTimeMillis;
        }
    }

}
